import { isFalsy } from 'utils/falsy';

import { getPersistedWizardData, hasWizardRelevantFields, isPasswordField, isTopicField, isUserField } from './wizard';
import benthosSchemaFull from '../../../../assets/rp-connect-schema-full.json' with { type: 'json' };
import {
  CRITICAL_CONNECTION_FIELDS,
  NON_CRITICAL_CONFIG_OBJECTS,
  REDPANDA_SECRET_COMPONENTS,
} from '../types/constants';
import {
  type BenthosSchemaFull,
  CONNECT_COMPONENT_TYPE,
  type ConnectComponentSpec,
  type ConnectComponentType,
  type ConnectConfigKey,
  type ConnectConfigObject,
  type RawComponentSpec,
  type RawFieldSpec,
} from '../types/schema';

const convertFieldSpecFromFullSchema = (fieldSpec: RawFieldSpec): RawFieldSpec => {
  const spec: RawFieldSpec = {
    name: fieldSpec.name || '',
    type: fieldSpec.type || 'unknown',
    kind: fieldSpec.kind || 'scalar',
    description: fieldSpec.description,
    is_advanced: fieldSpec.is_advanced,
    is_deprecated: fieldSpec.is_deprecated,
    is_optional: fieldSpec.is_optional,
    is_secret: fieldSpec.is_secret,
    default: fieldSpec.default,
    interpolated: fieldSpec.interpolated,
    bloblang: fieldSpec.bloblang,
    examples: fieldSpec.examples,
    annotated_options: fieldSpec.annotated_options,
    options: fieldSpec.options,
    version: fieldSpec.version,
    linter: fieldSpec.linter,
    scrubber: fieldSpec.scrubber,
  };

  if (fieldSpec.children && Array.isArray(fieldSpec.children)) {
    spec.children = fieldSpec.children.map(convertFieldSpecFromFullSchema);
  }

  return spec;
};

const createComponentConfigSpec = (component: RawComponentSpec): RawFieldSpec => {
  const config = component.config;

  const configSpec: RawFieldSpec = {
    name: component.name,
    type: 'object',
    kind: 'scalar',
    children: [],
  };

  if (config?.children && Array.isArray(config.children)) {
    configSpec.children = config.children.map(convertFieldSpecFromFullSchema);
  }

  return configSpec;
};

const typeToSchemaKey: Record<Exclude<ConnectComponentType, 'custom'>, keyof BenthosSchemaFull> = {
  input: 'inputs',
  output: 'outputs',
  processor: 'processors',
  cache: 'caches',
  buffer: 'buffers',
  rate_limit: 'rate-limits',
  scanner: 'scanners',
  metrics: 'metrics',
  tracer: 'tracers',
};

const typeToYamlConfigKey: Record<Exclude<ConnectComponentType, 'custom'>, ConnectConfigKey> = {
  input: 'input',
  output: 'output',
  processor: 'pipeline',
  cache: 'cache_resources',
  buffer: 'buffer',
  rate_limit: 'rate_limit_resources',
  scanner: 'scanner',
  metrics: 'metrics',
  tracer: 'tracer',
};

/**
 * Phase 0: Parses benthos schema and returns all components with metadata
 * @returns all components with metadata
 */
const parseSchema = () => {
  const schemaData = benthosSchemaFull;
  const allComponents: ConnectComponentSpec[] = [];

  // Parse each component type from flat arrays
  for (const componentType of CONNECT_COMPONENT_TYPE) {
    if (componentType === 'custom') {
      continue;
    }
    const schemaKey = typeToSchemaKey[componentType];
    const componentsArray = schemaData[schemaKey] as RawComponentSpec[] | undefined;

    if (!(componentsArray && Array.isArray(componentsArray))) {
      continue;
    }

    for (const comp of componentsArray) {
      const componentSpec: ConnectComponentSpec = {
        name: comp.name,
        type: comp.type as Exclude<ConnectComponentType, 'custom'>,
        status: comp.status || 'stable',
        plugin: comp.plugin,
        summary: comp.summary,
        description: comp.description,
        categories: comp.categories || undefined,
        config: createComponentConfigSpec(comp),
        version: comp.version || '1.0.0',
      };

      allComponents.push(componentSpec);
    }
  }

  return allComponents;
};

// Singleton cache for parsed components (lazy-loaded to avoid circular dependencies)
let _builtInComponentsCache: ConnectComponentSpec[] | undefined;

/**
 * Gets built-in components, parsing schema on first call and caching result
 * This lazy initialization prevents circular dependency issues during module loading
 */
export const getBuiltInComponents = (): ConnectComponentSpec[] => {
  if (!_builtInComponentsCache) {
    _builtInComponentsCache = parseSchema();
  }
  return _builtInComponentsCache;
};

/**
 * Phase 1: Converts a component specification to a config object structure with default values
 * following the Redpanda Connect YAML schema structure
 * @param componentSpec - the component spec to convert to a config object
 * @param showOptionalFields - whether to show optional fields
 * @returns ConnectConfigObject or undefined
 */
export const schemaToConfig = (
  componentSpec?: ConnectComponentSpec,
  showOptionalFields?: boolean
): Partial<ConnectConfigObject> | undefined => {
  if (!componentSpec?.config) {
    return;
  }

  const connectionConfig = generateDefaultValue(componentSpec.config, showOptionalFields, componentSpec.name);

  const config: Partial<ConnectConfigObject> = {};

  // Structure the config according to Redpanda Connect YAML schema
  switch (componentSpec.type) {
    case 'input':
    case 'output':
    case 'buffer':
    case 'metrics':
    case 'tracer':
      config[typeToYamlConfigKey[componentSpec.type]] = {
        [componentSpec.name]: connectionConfig,
      };
      break;

    case 'processor':
      config.pipeline = {
        processors: [
          {
            [componentSpec.name]: connectionConfig,
          },
        ],
      };
      break;

    case 'cache':
    case 'rate_limit':
      config[typeToYamlConfigKey[componentSpec.type]] = [
        {
          label: componentSpec.name,
          [componentSpec.name]: connectionConfig,
        },
      ];
      break;

    case 'scanner':
      // Scanners are embedded in inputs, return config directly
      return { [componentSpec.name]: connectionConfig };

    default:
      break;
  }

  return config;
};

function populateWizardFields(spec: RawFieldSpec): unknown | undefined {
  if (!spec.name) {
    return undefined;
  }

  const { topicData, userData } = getPersistedWizardData();

  // Populate topic fields
  if (isTopicField(spec.name) && topicData?.topicName) {
    return spec.kind === 'array' ? [topicData.topicName] : topicData.topicName;
  }

  // Populate username fields
  if (isUserField(spec.name) && userData?.username) {
    return userData.username;
  }

  // Populate password fields (empty string for manual entry)
  if (isPasswordField(spec.name) && userData?.username) {
    return '';
  }

  return undefined;
}

function shouldHideField(params: {
  spec: RawFieldSpec;
  showOptionalFields: boolean;
  componentName: string | undefined;
  hasWizardData: boolean;
  isExplicitlyRequired: boolean;
  isCriticalField: boolean;
  insideWizardContext: boolean;
}): boolean {
  const {
    spec,
    showOptionalFields,
    componentName,
    hasWizardData,
    isExplicitlyRequired,
    isCriticalField,
    insideWizardContext,
  } = params;
  // Hide non-critical config objects for REDPANDA_SECRET_COMPONENTS
  const isNonCriticalConfigObject =
    componentName &&
    spec.name &&
    REDPANDA_SECRET_COMPONENTS.includes(componentName) &&
    NON_CRITICAL_CONFIG_OBJECTS.has(spec.name);

  if (isNonCriticalConfigObject && !showOptionalFields && !isExplicitlyRequired) {
    return true;
  }

  const isAdvancedField = spec.is_advanced === true;
  if (isAdvancedField && !showOptionalFields && !hasWizardData && !isExplicitlyRequired) {
    const hasEmptyDefault = isFalsy(spec.default);
    const isEffectivelyOptional = spec.is_optional === true || (hasEmptyDefault && spec.is_optional !== false);
    if (!insideWizardContext || isEffectivelyOptional) {
      return true;
    }
  }

  const isOptionalField = spec.is_optional === true && !isAdvancedField;
  if (isOptionalField && !showOptionalFields && !hasWizardData) {
    return true;
  }

  const hasDefault = spec.default !== undefined;
  const isScalarDefault = hasDefault && spec.kind === 'scalar' && spec.type !== 'object';
  const isExplicitlyOptional = spec.is_optional === true;

  if (isScalarDefault && !showOptionalFields && !insideWizardContext && !isExplicitlyRequired) {
    // Always hide explicitly optional fields with defaults
    if (isExplicitlyOptional && !hasWizardData) {
      return true;
    }
    // For REDPANDA_SECRET_COMPONENTS, hide non-critical scalar fields with defaults
    if (componentName && REDPANDA_SECRET_COMPONENTS.includes(componentName) && !(isCriticalField || hasWizardData)) {
      return true;
    }
  }

  return false;
}

function generateObjectValue(
  spec: RawFieldSpec,
  showOptionalFields: boolean,
  componentName: string | undefined,
  insideWizardContext: boolean
): Record<string, unknown> | undefined {
  if (!spec.children) {
    return {};
  }

  const obj = {} as Record<string, unknown>;

  const { userData } =
    componentName && REDPANDA_SECRET_COMPONENTS.includes(componentName) ? getPersistedWizardData() : { userData: null };

  const hasDirectWizardChildren =
    spec.children?.some((child) => {
      if (child.name && isUserField(child.name) && userData?.username) {
        return true;
      }
      if (child.name && isPasswordField(child.name) && userData?.username) {
        return true;
      }
      return false;
    }) ?? false;

  const inWizardContext = insideWizardContext || hasDirectWizardChildren;
  for (const child of spec.children) {
    const childValue = generateDefaultValue(child, showOptionalFields, componentName, inWizardContext);

    if (childValue !== undefined && child.name) {
      obj[child.name] = childValue;
    }
  }

  const objKeys = Object.keys(obj);
  if (objKeys.length === 0) {
    const isExplicitlyRequired = spec.is_optional === false;
    const hasWizardData = hasWizardRelevantFields(spec, componentName);

    const shouldHideEmpty =
      (spec.is_optional === true || spec.is_advanced === true) &&
      !showOptionalFields &&
      !hasWizardData &&
      !isExplicitlyRequired;

    if (shouldHideEmpty) {
      return undefined;
    }

    // For REDPANDA_SECRET_COMPONENTS: hide empty objects without wizard-relevant data
    if (
      componentName &&
      REDPANDA_SECRET_COMPONENTS.includes(componentName) &&
      !(hasWizardData || showOptionalFields || isExplicitlyRequired)
    ) {
      return undefined;
    }
  }

  return obj;
}

function generateArrayValue(params: {
  spec: RawFieldSpec;
  showOptionalFields: boolean;
  componentName: string | undefined;
  isExplicitlyRequired: boolean;
  hasWizardData: boolean;
}): unknown[] | undefined {
  const { spec, showOptionalFields, componentName, isExplicitlyRequired, hasWizardData } = params;
  // Special case: SASL arrays for redpanda/kafka_franz components
  const isSaslArray = spec.name?.toLowerCase() === 'sasl';
  if (isSaslArray && spec.children && componentName && REDPANDA_SECRET_COMPONENTS.includes(componentName)) {
    const { userData } = getPersistedWizardData();

    if (userData?.username) {
      const saslObj = {} as Record<string, unknown>;

      for (const child of spec.children) {
        const childValue = generateDefaultValue(child, showOptionalFields, componentName, true);
        if (childValue !== undefined && child.name) {
          saslObj[child.name] = childValue;
        }
      }

      if (Object.keys(saslObj).length > 0) {
        return [saslObj];
      }
    }
  }
  const shouldHideArray =
    (spec.is_optional === true || spec.is_advanced === true) &&
    !showOptionalFields &&
    !hasWizardData &&
    !isExplicitlyRequired;

  if (shouldHideArray) {
    return undefined;
  }

  return [];
}

function generateScalarValue(
  spec: RawFieldSpec,
  showOptionalFields: boolean,
  componentName: string | undefined,
  insideWizardContext: boolean
): unknown {
  switch (spec.type) {
    case 'string':
      return '';
    case 'int':
    case 'float':
      return 0;
    case 'bool':
      return false;
    case 'object':
      return generateObjectValue(spec, showOptionalFields, componentName, insideWizardContext) ?? {};
    default:
      return '';
  }
}

export function generateDefaultValue(
  spec: RawFieldSpec,
  showOptionalFields?: boolean,
  componentName?: string,
  insideWizardContext?: boolean
): RawFieldSpec['default'] {
  // Generate schema comment for YAML generation
  if (spec.default !== undefined) {
    spec.comment = `Default: ${JSON.stringify(spec.default)}`;
  } else if (spec.is_optional === false) {
    spec.comment = 'Required';
  }

  // Try wizard data population first for Redpanda secret components
  if (componentName && REDPANDA_SECRET_COMPONENTS.includes(componentName)) {
    const wizardValue = populateWizardFields(spec);
    if (wizardValue !== undefined) {
      return wizardValue;
    }
  }

  // Determine field properties
  const isExplicitlyRequired = spec.is_optional === false;
  const isCriticalField = spec.name ? CRITICAL_CONNECTION_FIELDS.has(spec.name) : false;
  const hasWizardData = hasWizardRelevantFields(spec, componentName);

  // Check if field should be hidden
  if (
    shouldHideField({
      spec,
      showOptionalFields: showOptionalFields ?? false,
      componentName,
      hasWizardData,
      isExplicitlyRequired,
      isCriticalField,
      insideWizardContext: insideWizardContext ?? false,
    })
  ) {
    return undefined;
  }

  // Return default if available
  if (spec.default !== undefined) {
    return spec.default;
  }

  // Generate value based on field kind
  switch (spec.kind) {
    case 'scalar':
      return generateScalarValue(spec, showOptionalFields ?? false, componentName, insideWizardContext ?? false);
    case 'array':
      return generateArrayValue({
        spec,
        showOptionalFields: showOptionalFields ?? false,
        componentName,
        isExplicitlyRequired,
        hasWizardData,
      });
    case '2darray':
      return [];
    case 'map':
      return {};
    default:
      return undefined;
  }
}
